/*
 * author      : dormi330
 * date        : 2018/6/27
 * project     : mybatis
 * description : 支付一个已经存在的订单
 */

package com.deep.trade.domain.order;

import com.deep.trade.common.CommonUtils;
import com.deep.trade.common.TradeException;
import com.deep.trade.adaptor.required.DistributeLockService;
import com.deep.trade.adaptor.required.IdService;
import com.deep.trade.adaptor.required.payment.PayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Service
public class CommandPayOrder {
    private static final int ONLY_ONE_SHOULD_BE_UPDATED = 1;
    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
    private PayService payService;
    private IdService idService;
    private OrderQueryRepository orderQueryRepository;
    private OrderRepository orderRepository;
    private DistributeLockService dLockService;

    @Autowired
    public CommandPayOrder(
            IdService idService
            , OrderQueryRepository orderQueryRepository
            , OrderRepository orderRepository
            , DistributeLockService dLockService
            , PayService payService
    ) {
        this.idService = idService;
        this.orderQueryRepository = orderQueryRepository;
        this.orderRepository = orderRepository;
        this.dLockService = dLockService;
        this.payService = payService;
    }

    @Transactional
    public void payOrderSummary(OrderSummary orderSummary, String payChannel, String payType) throws Exception {
        logger.info("{}, orderSummary={}, payChannel={}, payType={}", CommonUtils.currentClassAndMethod(), orderSummary, payChannel, payType);
        if (null == orderSummary) throw new TradeException("argument cannot be null");

        // 检测订单是否可以支付
        List<TradeOrder> tradeOrderList = orderQueryRepository.tradeOderOfOrderSummary(orderSummary.getId());
        orderSummary.addTradeOrderList(tradeOrderList);
        if (!orderSummary.canBePaid()) {
            throw new TradeException("the order cannot be paid, orderSummaryId=" + orderSummary.getId());
        }

        String lockName = "orderSummary-" + "-" + orderSummary.getId();

        try {
            // 分布式锁, 防止 doPay多次, 考虑极端情况下, 使用多个渠道对这个订单进行支付
            dLockService.getLock(lockName);
            doPay(orderSummary.getPayMoney(), orderSummary.getId(), payChannel);
        } catch (Exception ex) {
            // 支付失败, 可以继续支付, 状态不进行变更
            ex.printStackTrace();
            logger.error(ex.toString());
            throw new TradeException("order fail due to pay service failed");
        } finally {
            dLockService.releaseLock(lockName);
        }

        // TODO 整个情况应该是回调通知
        logger.error("TODO 整个情况应该是回调通知, 而不是直接调用");
        afterOrderSummaryPaid(payChannel, payType, orderSummary, tradeOrderList);
    }

    @Transactional
    public void payTradeOrder(TradeOrder tradeOrder, String payChannel, String payType) throws Exception {
        logger.info("{}, orderSummary={}, tradeOrder={}, payChannel={}, payType={}", CommonUtils.currentClassAndMethod(), tradeOrder, payChannel, payType);
        if (null == tradeOrder) throw new TradeException("tradeOrder cannot be null");

        // 检测订单是否可以支付
        if (!tradeOrder.canBePaid()) {
            throw new TradeException("order cannot be paid, status=" + tradeOrder.getStatus());
        }

        String lockName = "tradeOrder-" + "-" + tradeOrder.getId();
        try {
            // 分布式锁, 防止 doPay多次
            dLockService.getLock(lockName);
            doPay(tradeOrder.getPayMoney(), tradeOrder.getId(), payChannel);
        } catch (Exception ex) {
            // 如果支付失败, 可以继续支付, 状态不进行变更
            ex.printStackTrace();
            logger.error(ex.toString());
            throw new TradeException("pay order fail due to pay service failed");
        } finally {
            dLockService.releaseLock(lockName);
        }

        // TODO 整个情况应该是回调通知
        logger.warn("TODO 整个情况应该是回调通知, 而不是直接调用");
        afterTradeOrderPaid(tradeOrder, payChannel, payType);
    }

    /**
     * 正常情况下, mq消息订阅, 触发这个方法的调用, 所以是 public
     * 订单【支付成功】后的动作, 包括 更新 tradeOrder + 插入一条 OrderPayInfo记录
     */
    @Transactional
    public void afterTradeOrderPaid(TradeOrder tradeOrder, String payChannel, String payType) throws Exception {
        logger.info("{}, orderSummary={}, tradeOrder={}, payChannel={}, payType={}", CommonUtils.currentClassAndMethod(), tradeOrder, payChannel, payType);

        tradeOrder.setStatusEnum(OrderStatusEnum.PAID);
        if (ONLY_ONE_SHOULD_BE_UPDATED != orderRepository.updateTradeOrderStatus(tradeOrder)) {
            throw new TradeException("fail: update trade order after paid ");
        }

        // TODO 更新 OrderPayment
        OrderPayment orderPayment = new OrderPayment();
        orderPayment.setId(idService.uuid());
        orderPayment.setCreatedBy(tradeOrder.getMemberId());
        orderPayment.setUpdatedBy(tradeOrder.getMemberId());
        //
        orderPayment.setTradeOrderId(tradeOrder.getId());
        orderPayment.setPayType(payType);
        orderPayment.setPayChannel(payChannel);
        orderPayment.setPayStatus(OrderStatusEnum.PAID.name);
        orderPayment.setPaidAt(new Date());
        orderPayment.setPayMoney(tradeOrder.getPayMoney());

        orderRepository.addOrderPayment(orderPayment);
    }

    // 进行支付, 实际支付待定 TODO
    private void doPay(BigDecimal money, String orderNo, String payChannel) {
        payService.pay(money, orderNo, payChannel);
    }

    /**
     * 正常情况下, mq消息订阅, 触发这个方法的调用, 所以是 public
     * 订单【支付成功】后的动作
     */
    @Transactional
    public void afterOrderSummaryPaid(String payChannel, String payType, OrderSummary orderSummary, List<TradeOrder> tradeOrderList) throws Exception {
        // 更新 orderSummary
        orderSummary.setPaidAt(new Date());
        orderSummary.setPayChannel(payChannel);
        orderSummary.setUpdatedBy("_SYSTEM_");  // 系统更新
        if (ONLY_ONE_SHOULD_BE_UPDATED != orderRepository.updateOrderSummaryToPaid(orderSummary)) {
            throw new TradeException("fail: update order summary after paid ");
        }

        // 更新 tradeOrder
        for (TradeOrder tradeOrder : tradeOrderList) {
            afterTradeOrderPaid(tradeOrder, payChannel, payType);
        }
    }
}
